package br.com.hs.nfe.certificado;

import br.com.hs.nfe.certificado.exception.HSCertificadoInvalidoException;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.Date;
import java.util.Enumeration;
import java.util.Map;
import org.bouncycastle.asn1.ASN1InputStream;
import org.bouncycastle.asn1.DERBoolean;
import org.bouncycastle.asn1.DERInteger;
import org.bouncycastle.asn1.DERObject;
import org.bouncycastle.asn1.DEROctetString;
import org.bouncycastle.asn1.DERSequence;

/**
 * Classe responsável por auxiliar nas regras de negócio do certificado.
 * @author Ranlive Hrysyk
 */
public class CertificadoHelper {

    /**
     * Retorna uma string com o CNPJ do certificado.
     */
    public static String recuperarCNPJ(X509Certificate certificado) throws HSCertificadoInvalidoException {
        Map nomesAlternativos;
        try {
            nomesAlternativos = new NomesAlternativos().recuperarNomesAlternativos(certificado);
        } catch (Exception e) {
            throw new HSCertificadoInvalidoException(CertificadoConstants.ERRO_RECUPERAR_CNPJ);
        }

        String cnpj = (String) nomesAlternativos.get("2.16.76.1.3.3");
        if ((cnpj == null) || (cnpj.equals(""))) {
            throw new HSCertificadoInvalidoException(CertificadoConstants.ERRO_RECUPERAR_CNPJ);
        }
        return (String) nomesAlternativos.get("2.16.76.1.3.3");
    }

    /**
     * Retorna o assunto do certificado.
     */
    public static String getAssunto(X509Certificate certificado) {
        return certificado.getSubjectDN().toString();
    }

    /**
     * Retorna o emissor do certificado.
     */
    public static String getEmissor(X509Certificate certificado) {
        return certificado.getIssuerDN().toString();
    }

    /**
     * Retorna a data de validade do certificado.
     */
    public static Date getDataValidadeFim(X509Certificate certificado) {
        return certificado.getNotAfter();
    }

    /**
     * Retorna a data de inicio da validade do certificado.
     */
    public static Date getDataValidadeInicio(X509Certificate certificado) {
        return certificado.getNotBefore();
    }

    /**
     * Converte um certificado byte array em X509Certificate.
     */
    public static X509Certificate getX509(byte[] binaryCertificate) throws HSCertificadoInvalidoException {
        CertificateFactory certificateFactory = null;
        X509Certificate certImpl = null;
        try {
            certificateFactory = CertificateFactory.getInstance("X.509");

            ByteArrayInputStream arrayInputStream = new ByteArrayInputStream(binaryCertificate);

            certImpl = (X509Certificate) certificateFactory.generateCertificate(arrayInputStream);
        } catch (Exception e) {
            throw new HSCertificadoInvalidoException(CertificadoConstants.ERRO_PARSER_CERTIFICADO);
        }
        return certImpl;
    }

    /**
     * Converte em X509Certificate em um byte array.
     */
    public static byte[] getBytes(X509Certificate certificate) throws HSCertificadoInvalidoException {
        byte[] retorno = null;
        try {
            retorno = certificate.getEncoded();
        } catch (CertificateEncodingException ex) {
            throw new HSCertificadoInvalidoException(CertificadoConstants.ERRO_PARSER_CERTIFICADO);
        }
        return retorno;
    }

    /**
     * Valida a versão do certificado.
     */
    public static void validarVersao(X509Certificate certificado)
            throws HSCertificadoInvalidoException {
        if (certificado.getVersion() != 3) {
            throw new HSCertificadoInvalidoException(CertificadoConstants.MSG_CERTIFICADO_VERSAO);
        }
    }

    /**
     * Transforma um array de bytes em DERObject
     */
    private static DERObject toDERObject(byte[] data) throws IOException {
        ByteArrayInputStream inStream = new ByteArrayInputStream(data);
        ASN1InputStream derInputStream = new ASN1InputStream(inStream);
        return derInputStream.readObject();
    }

    /**
     * Valida se o certificado é de usuário final
     */
    public static void validarEntidadeFinal(X509Certificate certificado) throws HSCertificadoInvalidoException {
        DERBoolean isCa = new DERBoolean(false);
        try {
            byte[] extValues = certificado.getExtensionValue("2.5.29.19");
            if (extValues != null) {
                DERObject derObject = toDERObject(extValues);

                DEROctetString derOctetString = (DEROctetString) derObject;
                DERSequence derSequence = (DERSequence) toDERObject(derOctetString.getOctets());

                Enumeration en = derSequence.getObjects();
                if (en.hasMoreElements()) {
                    isCa = (DERBoolean) en.nextElement();
                }

                if (en.hasMoreElements()) {
                    DERInteger len = (DERInteger) en.nextElement();
                }
            }
        } catch (IOException io) {
        } catch (RuntimeException io) {
        } catch (Exception io) {
        }
        if (isCa.isTrue()) {
            throw new HSCertificadoInvalidoException(CertificadoConstants.MSG_CERTIFICADO_ENTIDADE_FINAL);
        }
    }

    /**
     * Valida se o certificado é não-repúdio.
     */
    public static void validarNaoRepudio(X509Certificate certificado) throws HSCertificadoInvalidoException {
        boolean[] keyUsage = certificado.getKeyUsage();
        if (!keyUsage[1]) {
            throw new HSCertificadoInvalidoException(CertificadoConstants.MSG_CERTIFICADO_NAO_REPUDIO);
        }
    }

    /**
     * Valida se o certificado pode ser usado para assinatura.
     */
    public static void validarCertificadoAssinatura(X509Certificate certificado) throws HSCertificadoInvalidoException {
        boolean[] keyUsage = certificado.getKeyUsage();
        if (!keyUsage[0]) {
            throw new HSCertificadoInvalidoException(CertificadoConstants.MSG_CERTIFICADO_ASSINATURA);
        }
    }

    /**
     * Valida o período de validade do certificado.
     */
    public static void validarPeriodo(X509Certificate certificado) throws HSCertificadoInvalidoException {
        Date dataValidacao = new Date();
        if ((!certificado.getNotBefore().before(dataValidacao)) || (!certificado.getNotAfter().after(dataValidacao))) {
            throw new HSCertificadoInvalidoException(CertificadoConstants.MSG_CERTIFICADO_PERIODO);
        }
    }

    /**
     * Valida se o certificado possui o CNPJ.
     */
    public static void validarOtherNameCNPJ(X509Certificate certificado) throws HSCertificadoInvalidoException {
        try {
            Map nomesAlternativos = new NomesAlternativos().recuperarNomesAlternativos(certificado);
            if (!nomesAlternativos.containsKey("2.16.76.1.3.3")) {
                throw new HSCertificadoInvalidoException(CertificadoConstants.MSG_CERTIFICADO_CNPJ);
            }
        } catch (Exception e) {
            throw new HSCertificadoInvalidoException(CertificadoConstants.MSG_CERTIFICADO_CNPJ);
        }
    }

    /**
     * Valida se o certificado é do tipo autenticação de cliente.
     */
    public static void validarCertificadoAutenticacao(X509Certificate certificado) throws HSCertificadoInvalidoException {
        try {
            if (!certificado.getExtendedKeyUsage().contains("1.3.6.1.5.5.7.3.2")) {
                throw new HSCertificadoInvalidoException(CertificadoConstants.MSG_CERTIFICADO_AUTENTICACAO);
            }
        } catch (Exception e) {
            throw new HSCertificadoInvalidoException(CertificadoConstants.MSG_CERTIFICADO_AUTENTICACAO);
        }
    }
}
